<?php
	/* developer	: BREEZEER
	 * description	: split big file to small file
	 * create time	: 2022/5/7 11:18
	 *
	 *
	 */

	namespace App\common;

	interface iFileSpliter {
		
		# source file path
		#
		public function setSourceFileFullPath				(string $str_file_full_path)			: iFileSpliter;

		# single file block max size with bytes
		#
		public function setFileMaxBlockSize					(int	$file_max_block_size/* bytes*/)	: iFileSpliter;

		# destination file save directory
		#
		public function setDestinationFileSaveDirectory		(string	$str_dest_file_save_directory)	: iFileSpliter;

		# split
		#
		public function splitBigFileToSmallFile				()										: iFileSpliter;

		# foreach all small file
		#
		public function forEeachAllSmallFile				($callback_one_file=null)				: iFileSpliter;	

		# free
		#
		public function free()																		: iFileSpliter;
	}
	class cFileException extends \Exception 
	{
	}
	class cFileSpliter implements iFileSpliter
	{
		public function __construct()
		{

			#
			#
			$this->m_ar_small_file					= array()	;
			$this->m_file_max_block_size			= 0			;
			$this->m_str_dest_file_save_directory	= ''		;
			$this->m_str_file_full_path				= ''		;
		}
		public function setSourceFileFullPath (string $str_file_full_path) : iFileSpliter
		{
			$this->m_str_file_full_path				= $str_file_full_path;
			
			# source file exist ?
			#
			if(file_exists($this->m_str_file_full_path)){}else{
				
				throw new cFileException("source file : ".$this->m_str_file_full_path." not found.");
			}

			# source file path information
			#
			$this->m_str_file_full_path_information	= pathinfo($this->m_str_file_full_path);

			#
			#
			return $this;
		}
		public function setFileMaxBlockSize (int	$file_max_block_size/* bytes*/) : iFileSpliter{
			
			# file block size must over zero bytes
			#
			$this->m_file_max_block_size			= $file_max_block_size;if($this->m_file_max_block_size>0){}else{
				
				throw new cFileException(" file block size must over zero bytes ,but you set block size with : ".$this->m_file_max_block_size);
			}

			return $this;
		}
		public function setDestinationFileSaveDirectory (string	$str_dest_file_save_directory) : iFileSpliter{

			$this->m_str_dest_file_save_directory	= $str_dest_file_save_directory;

			# destination directory must exist and can be written data permission ?
			#
			{
				if(file_exists($this->m_str_dest_file_save_directory)){}else{
				
					throw new cFileException("destination directory : ".$this->m_str_dest_file_save_directory." not found.");
				}

				if(is_writable($this->m_str_dest_file_save_directory)){}else{
					
					throw new cFileException("destination directory :".$this->m_str_dest_file_save_directory." can not be written");
				}
			}

			return $this;
		}

		/*  split source big file to small file
		 *
		 *
		 */
		public function splitBigFileToSmallFile () : iFileSpliter
		{
			# initialization
			#
			$this->free();
			
			#
			#
			if($this->m_str_file_full_path=='' || $this->m_str_dest_file_save_directory=='')
			{
				throw new cFileException("please input your source file and destination directory before split file");
			}

			# source file size must over zero
			#
			$file_source_size			= filesize($this->m_str_file_full_path);if($file_source_size>0)
			{
			}
			else
			{
				throw new cFileException("source file size ".$file_source_size ." bytes , error file path : ".$this->m_str_file_full_path);
			}
			
			# open file with read only mode
			#
			$fp							= fopen($this->m_str_file_full_path,'rb');
			
			# small file name index based zero
			#
			#
			$b_source_file_read_ok		= false;
			$n_small_file_name_index	= 0;
			$str_small_file_name_uuid	= app()->gcHelper->gfCreateUUID();
			for(;;) 
			{
				# file splitter have completed return
				#
				if($b_source_file_read_ok)
				{
					break;
				}
				
				# buffer size
				#
				$n_buffer					= 1024*8 /* 8k */;
				
				#
				#
				$buffer_remain				= '';
				
				# create a small file
				#
				{
					$str_small_file_name	= $this->m_str_dest_file_save_directory.'/'.$this->m_str_file_full_path_information['basename'].'.'.$str_small_file_name_uuid.'.'.($n_small_file_name_index++);
					$fp_small				= fopen($str_small_file_name,'w+b');
					
					# if exist remain buffer ,i will write it to small file first before continue read source file content
					#
					$n_small_file_size		= 0;if($buffer_remain=='')
					{
					}
					else
					{
						fwrite($fp_small,$buffer_remain);
						$n_small_file_size	= strlen($buffer_remain);
					}
					for(;;)
					{
						$buffer	= fread($fp,$n_buffer);if($buffer===false||$buffer=='')
						{
							# file read error
							#
							$b_source_file_read_ok		= true;
							break;
						}
						else
						{
							# have read file size => actually have read file size this time remember it may be less than you want read file size
							#
							$n_read_size				= strlen($buffer);	

							# check one file block ok ?
							#
							$n_small_file_remain_bytes	= $this->m_file_max_block_size /* one max block size */ - $n_small_file_size/* have read size */;if($n_small_file_remain_bytes >= $n_read_size)
							{
								# write to small file
								#
								fwrite($fp_small,$buffer);
								$n_small_file_size		= $n_small_file_size + $n_read_size;	
							}
							else
							{
								# file block size ok ?
								#
								#
								# |==================================================================================================| buffer data size [ $n_read_size ]
								# |--------------------------------------------------------------|===================================|
								# |< last can be written size [$n_last_written_size]			>|< buffer remain					>|
								#
								#
								$n_last_written_size	= $n_read_size - $n_small_file_remain_bytes									;
								$buffer_last_write		= substr($buffer,0,$n_last_written_size)									;
								$buffer_remain			= substr($buffer,$n_last_written_size)										;

								# write
								#
								fwrite($fp_small,$buffer_last_write);
								$n_small_file_size		= $n_small_file_size + $n_last_written_size									;	
								break;
							}
						}
					}

					# close small file
					#
					fclose($fp_small);

					# get one small file successfully
					#
					$this->m_ar_small_file []			= $str_small_file_name;
				}
			}
			fclose($fp);

			return $this;
		}
		public function forEeachAllSmallFile ($callback_one_file=null) : iFileSpliter
		{
			foreach($this->m_ar_small_file as $one_file)
			{
				$file_size = filesize($one_file);
				$file_path = $one_file			;

				#
				#
				$callback_one_file	(/*app()->gcHelper->gfConvertArrayToStdClass*/([
									
										'file_size'	=> $file_size,
										'file_path'	=> $file_path,
									]));
			}
			return $this;
		}
		public function free() : iFileSpliter
		{
			if(is_array($this->m_ar_small_file))
			{
				foreach($this->m_ar_small_file as $one_file)
				{
					# delete temporary files
					#
					unlink ($one_file);
				}
				unset($this->m_ar_small_file);$this->m_ar_small_file=array();
			}
			return $this;
		}
		public function getSourceFileInformation()
		{
			return stat($this->m_str_file_full_path);
		}
	}